package optimizers.simplega;

import java.io.Serializable;
import java.util.Random;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;

import model.OligoSystemGeneral;

import fitness.FitnessResult;

import optimizers.commons.GAConstants;
import optimizers.commons.RunConfig;
import optimizers.commons.Static;
import optimizers.simplega.Constants;
import parallelism.OligoSystemGeneralFitness;
import parallelism.OligoSystemGeneralFitnessEvaluationTask;
import util.MyUtil;
import visualization.PlotExpData;

public abstract class Individual implements Cloneable, Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public int nSimpleSequences;
	public double[][][] templates;
	public double[][] seqK;
	public double[][] inhK;
	public double[][] seqConcentration;
	public FitnessResult fitnessResult;
	public String mutationInfo;
	public String modelString;
	public Individual parent;
	public int generationIndex;
	public int individualIndex;
	public RunConfig config;

	private Random rand = new Random();

	public Individual(int nSimpleSequences, RunConfig config) {
		this.config = config;
		this.nSimpleSequences = nSimpleSequences;
		this.templates = new double[nSimpleSequences + 1][nSimpleSequences][nSimpleSequences];
		this.seqK = new double[nSimpleSequences + 1][nSimpleSequences];
		this.inhK = new double[nSimpleSequences + 1][nSimpleSequences];
		this.seqConcentration = getSequenceConcentration(nSimpleSequences);

		for (int i = 0; i < nSimpleSequences; i++) {
			seqK[0][i] = MyUtil.randomDoubleLogScale(rand,
					Constants.gKseqLibMin, Constants.gKseqLibMax);
		}

		templates[0][0][0] = 10;
		templates[0][0][1] = 10;
		templates[2][0][0] = 10;

		// int j = 1+rand.nextInt(nSimpleSequences-1);
		// templates[0][j][0] = MyUtil.randomDouble(rand,
		// Constants.gActTemplateInitConcentrationLibMin,
		// Constants.gActTemplateInitConcentrationLibMax);

		/*
		 * for (int i = 0; i < nSimpleSequences; ++i) { for (int j = 0; j <
		 * nSimpleSequences; ++j) { if (this.rand.nextDouble() <
		 * GAConstants.ActivationProbability) { templates[0][i][j] =
		 * MyUtil.randomDouble(rand,
		 * Constants.gActTemplateInitConcentrationLibMin,
		 * Constants.gActTemplateInitConcentrationLibMax); if
		 * (this.rand.nextDouble() < GAConstants.InhibitionProbability) { int k
		 * = rand.nextInt(nSimpleSequences); templates[k + 1][i][j] =
		 * MyUtil.randomDouble(rand,
		 * Constants.gActTemplateInitConcentrationLibMin,
		 * Constants.gActTemplateInitConcentrationLibMax); } } else {
		 * templates[0][i][j] = 0; } } }
		 */

	}

	public abstract double[][] getSequenceConcentration(int nSimpleSequences);

	private boolean hasConnectionTo(int i) {
		for (int j = 0; j < nSimpleSequences; j++) {
			if (templates[0][j][i] > 0) {
				return true;
			}
		}
		return false;
	}

	public String mutateActivation() {
		int i = rand.nextInt(nSimpleSequences);
		rand = new Random();
		int j = rand.nextInt(nSimpleSequences);
		if (templates[0][i][j] > 0) {
			for (int k = 0; k < nSimpleSequences + 1; k++) {
				templates[k][i][j] = 0;
				return "Mutate Delete Activation "
						+ common.Static.intToName.get(i) + "->"
						+ common.Static.intToName.get(j);
			}
		} else {
			templates[0][i][j] = MyUtil.randomDouble(rand,
					Constants.gActTemplateInitConcentrationLibMin,
					Constants.gActTemplateInitConcentrationLibMax);
			if (this.rand.nextDouble() < GAConstants.InhibitionProbability) {
				int k = rand.nextInt(nSimpleSequences);
				templates[k + 1][i][j] = MyUtil.randomDouble(rand,
						Constants.gActTemplateInitConcentrationLibMin,
						Constants.gActTemplateInitConcentrationLibMax);
			}
			return "Mutate Add Activation: " + common.Static.intToName.get(i)
					+ "->" + common.Static.intToName.get(j);
		}
		return mutateParameter();
	}

	public String mutateInhibition() {
		int i = rand.nextInt(nSimpleSequences);
		rand = new Random();
		int j = rand.nextInt(nSimpleSequences);
		int count = 0;
		while (templates[0][i][j] == 0) {
			i = rand.nextInt(nSimpleSequences);
			j = rand.nextInt(nSimpleSequences);
			count++;
			if (count == 50)
				return mutateParameter();
		}
		int k = rand.nextInt(nSimpleSequences);
		if (templates[k + 1][i][j] > 0) {
			if (rand.nextBoolean()) {
				templates[k + 1][i][j] = 0;
				return "Mutate Delete Inhibition "
						+ common.Static.intToName.get(k) + "->I"
						+ common.Static.intToName.get(i) + ""
						+ common.Static.intToName.get(j);

			} else {
				int l = rand.nextInt(nSimpleSequences);
				count = 0;
				while (templates[l + 1][i][j] > 0) {
					l = rand.nextInt(nSimpleSequences);
					count++;
					if (count == 50)
						return mutateParameter();
				}
				templates[l + 1][i][j] = MyUtil.randomDouble(rand,
						Constants.gActTemplateInitConcentrationLibMin,
						Constants.gActTemplateInitConcentrationLibMax);
				return "Mutate Add Inhibition "
						+ common.Static.intToName.get(l) + "->I"
						+ common.Static.intToName.get(i) + ""
						+ common.Static.intToName.get(j);
			}
		} else {
			int l = k;
			templates[l + 1][i][j] = MyUtil.randomDouble(rand,
					Constants.gActTemplateInitConcentrationLibMin,
					Constants.gActTemplateInitConcentrationLibMax);
			return "Mutate Add Inhibition " + common.Static.intToName.get(l)
					+ "->I" + common.Static.intToName.get(i) + ""
					+ common.Static.intToName.get(j);
		}
	}

	public String mutateParameter() {
		StringBuilder info = new StringBuilder();
		info.append("Mutate Parameters:\nSeqK:\n");
		for (int i = 0; i < nSimpleSequences; i++) {
			if (seqK[0][i] > 0
					&& rand.nextDouble() < Constants.probGeneMutation) {
				double oldParameter = seqK[0][i];
				if (rand.nextDouble() < Constants.probRandomParamMutate) {
					seqK[0][i] = MyUtil.randomDoubleLogScale(rand,
							Constants.gKseqLibMin, Constants.gKseqLibMax);
				} else {
					seqK[0][i] = seqK[0][i]
							* (Constants.paramMutateGaussianFactor
									* rand.nextGaussian() + 1);
					if (seqK[0][i] < Constants.gKseqLibMin)
						seqK[0][i] = Constants.gKseqLibMin;
					if (seqK[0][i] > Constants.gKseqLibMax)
						seqK[0][i] = Constants.gKseqLibMax;
				}
				info.append("Seq " + common.Static.intToName.get(i) + ": "
						+ common.Static.df4.format(oldParameter) + "->"
						+ common.Static.df4.format(seqK[0][i]) + "\n");
			}
		}
		info.append("Templates:\n");
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				if (templates[0][i][j] > 0
						&& rand.nextDouble() < Constants.probGeneMutation) {
					double oldValue = templates[0][i][j];
					if (rand.nextDouble() < Constants.probRandomParamMutate) {
						templates[0][i][j] = MyUtil.randomDouble(rand,
								Constants.gActTemplateInitConcentrationLibMin,
								Constants.gActTemplateInitConcentrationLibMax);
					} else {
						templates[0][i][j] = templates[0][i][j]
								* (Constants.paramMutateGaussianFactor
										* rand.nextGaussian() + 1);
						if (templates[0][i][j] < Constants.gActTemplateInitConcentrationLibMin)
							templates[0][i][j] = Constants.gActTemplateInitConcentrationLibMin;
						if (templates[0][i][j] > Constants.gActTemplateInitConcentrationLibMax)
							templates[0][i][j] = Constants.gActTemplateInitConcentrationLibMax;
					}
					info.append(common.Static.intToName.get(i) + "->"
							+ common.Static.intToName.get(j) + ": "
							+ common.Static.df4.format(oldValue) + "->"
							+ common.Static.df4.format(templates[0][i][j])
							+ "\n");
				}
			}
		}

		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				for (int k = 0; k < nSimpleSequences; k++) {
					if (templates[i + 1][j][k] > 0
							&& rand.nextDouble() < Constants.probGeneMutation) {
						double oldValue = templates[i + 1][j][k];
						if (rand.nextDouble() < Constants.probRandomParamMutate) {
							templates[i + 1][j][k] = MyUtil
									.randomDouble(
											rand,
											Constants.gActTemplateInitConcentrationLibMin,
											Constants.gActTemplateInitConcentrationLibMax);
						} else {
							templates[i + 1][j][k] = templates[i + 1][j][k]
									* (Constants.paramMutateGaussianFactor
											* rand.nextGaussian() + 1);
							if (templates[i + 1][j][k] < Constants.gActTemplateInitConcentrationLibMin)
								templates[i + 1][j][k] = Constants.gActTemplateInitConcentrationLibMin;
							if (templates[i + 1][j][k] > Constants.gActTemplateInitConcentrationLibMax)
								templates[i + 1][j][k] = Constants.gActTemplateInitConcentrationLibMax;
							info.append(common.Static.intToName.get(i)
									+ "->I"
									+ common.Static.intToName.get(j)
									+ ""
									+ common.Static.intToName.get(k)
									+ ": "
									+ common.Static.df4.format(oldValue)
									+ "->"
									+ common.Static.df4
											.format(templates[i + 1][j][k])
									+ "\n");
						}
					}
				}
			}
		}
		return info.toString();
	}

	public void evaluateFitness() {

	}

	public OligoSystemGeneral getOligoModel() {
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				inhK[i + 1][j] = MyUtil.calculateInhK(seqK[0][i], seqK[0][j]);
				seqK[i + 1][j] = inhK[i + 1][j] / 3;
			}
		}
		return new OligoSystemGeneral(nSimpleSequences, templates, seqK, inhK,
				seqConcentration, Constants.defaultExo, common.Static.intToName,
				common.Static.nameToInt);
	}

	public OligoSystemGeneral getOligoModel(double[][][] templates) {
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				inhK[i + 1][j] = MyUtil.calculateInhK(seqK[0][i], seqK[0][j]);
				seqK[i + 1][j] = inhK[i + 1][j] / 3;
			}
		}
		return new OligoSystemGeneral(nSimpleSequences, templates, seqK, inhK,
				seqConcentration, Constants.defaultExo, common.Static.intToName,
				common.Static.nameToInt);
	}

	public void mutate() {
		if (rand.nextDouble() < Constants.probParamMutation) {
			mutationInfo = mutateParameter();
		} else if (rand.nextDouble() < Constants.probMutateActivation) {
			mutationInfo = mutateActivation();
		} else {
			mutationInfo = mutateInhibition();
		}
	}

	public JPanel printModelInTextArea() {
		JPanel info = new JPanel();
		JTabbedPane tabbedPane = new JTabbedPane();
		info.add(tabbedPane);
		JTextPane txtPane = new JTextPane();
		String text = "";
		text += this.toString();
		if (this.fitnessResult != null) {
			text += "Fitness: " + this.fitnessResult.toString() + "\n";
		}

		text += "Mutation: " + this.mutationInfo + "\n";
		txtPane.setText(text);

		JTextPane modelString = new JTextPane();
		modelString.setText(this.modelString);
		tabbedPane.addTab("NEAT", txtPane);
		tabbedPane.addTab("NATH", modelString);
		return info;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < nSimpleSequences; i++) {
			builder.append("Seq " + common.Static.intToName.get(i) + ": "
					+ common.Static.df4.format(seqK[0][i]) + "\n");
		}
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				if (templates[0][i][j] > 0) {
					builder.append(common.Static.intToName.get(i) + "->"
							+ common.Static.intToName.get(j) + ": "
							+ common.Static.df4.format(templates[0][i][j]) + "\n");
				}
			}
		}
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				for (int k = 0; k < nSimpleSequences; k++) {
					if (templates[i + 1][j][k] > 0) {
						builder.append(common.Static.intToName.get(i) + "->" + "I"
								+ common.Static.intToName.get(j) + ""
								+ common.Static.intToName.get(k) + ": "
								+ common.Static.df4.format(templates[i + 1][j][k])
								+ "\n");
					}
				}
			}
		}
		return builder.toString();
	}

	public double getFitness() {
		if (this.fitnessResult == null) {
			return 0;
		} else {
			return this.fitnessResult.finalResult();
		}
	}

	public JPanel displayTimeSereis() {
		OligoSystemGeneral oligoModel = getOligoModel();
		double[][] data = oligoModel.getTimeSeriesComplexModel();
		String[] seqNames = new String[data.length];
		for (int i = 0; i < data.length; i++) {
			seqNames[i] = oligoModel.intToName.get(i);
			if (seqNames[i] == null)
				seqNames[i] = String.valueOf(i);
		}
		final PlotExpData demo = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data, seqNames, "Time", "Expression");
		return (demo);
	}

	public double getNumberOfTemplates() {
		int result = 0;
		for (int i = 0; i < nSimpleSequences + 1; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				for (int k = 0; k < nSimpleSequences; k++) {
					if (templates[i][j][k] > 0)
						result++;
				}
			}
		}
		return result;
	}

	public double getStandardDeviationOfLogK() {
		int count = 0;
		double sum = 0;
		for (int i = 0; i <= nSimpleSequences; ++i) {
			for (int j = 0; j < nSimpleSequences; ++j) {
				if (seqK[i][j] > 0) {
					count++;
					sum += Math.log(seqK[i][j]);
				}
			}
		}
		double avg = sum / count;
		double sd = 0;
		for (int i = 0; i <= nSimpleSequences; ++i) {
			for (int j = 0; j < nSimpleSequences; ++j) {
				if (seqK[i][j] > 0) {
					sd += (Math.log(seqK[i][j]) - avg)
							* (Math.log(seqK[i][j]) - avg);
				}
			}
		}
		sd = Math.sqrt(sd / count);
		return sd;
	}

	public double[][][] deleteActivation(int i, int j) {
		double[][][] result = new double[nSimpleSequences + 1][nSimpleSequences][nSimpleSequences];
		MyUtil.multiArrayCopy(templates, result);
		for (int k = 0; k < nSimpleSequences + 1; k++) {
			result[k][i][j] = 0;
		}
		return result;
	}

	public double[][][] deleteInhibition(int i, int j, int k) {
		double[][][] result = new double[nSimpleSequences + 1][nSimpleSequences][nSimpleSequences];
		MyUtil.multiArrayCopy(templates, result);
		result[i + 1][j][k] = 0;
		return result;
	}

	public boolean prune(CompletionService<Object> cservice) {
		boolean pruned = false;
		evaluateFitness();
		int taskCount = 0;
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				if (templates[0][i][j] > 0) {
					OligoSystemGeneral oligoModel = getOligoModel(deleteActivation(
							i, j));
					cservice.submit(new OligoSystemGeneralFitnessEvaluationTask(
							0, i, j, oligoModel));
					taskCount++;
				}
			}
		}

		for (int ii = 0; ii < taskCount; ii++) {
			try {
				OligoSystemGeneralFitness result1 = (OligoSystemGeneralFitness) cservice
						.take().get();
				FitnessResult score = result1.fitness;
				int i = result1.i;
				int j = result1.j;
				int k = result1.k;
				System.out.println("Orig:" + fitnessResult.finalResult() + " "
						+ i + " " + j + " " + k + " " + score.finalResult()
						+ " " + result1.estimatedTime);
				if (fitnessResult.finalResult() < score.finalResult()) {
					pruned = true;
					if (i == 0) {
						this.templates = deleteActivation(j, k);
					} else {
						this.templates = deleteInhibition(i, j, k);
					}
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return pruned;
	}
}
